﻿using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using System.Runtime.CompilerServices;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Lightbug.Utilities
{
    public enum Direction
    {
        Right,
        Left,
        Up,
        Down,
        Forward,
        Back
    }

    /// <summary>
    /// This static class contains all kind of useful methods used across the package.
    /// </summary>
    public static class CustomUtilities
    {

        public static Vector3 Add(Vector3 vectorA, Vector3 vectorB)
        {
            vectorA.x += vectorB.x;
            vectorA.y += vectorB.y;

            return vectorA;
        }

        public static Vector3 Substract(Vector3 vectorA, Vector3 vectorB)
        {
            vectorA.x -= vectorB.x;
            vectorA.y -= vectorB.y;

            return vectorA;
        }

        public static Vector3 Multiply(Vector3 vectorValue, float floatValue)
        {
            vectorValue.x *= floatValue;
            vectorValue.y *= floatValue;
            vectorValue.z *= floatValue;

            return vectorValue;

        }

        public static Vector3 Multiply(Vector3 vectorValue, float floatValueA, float floatValueB)
        {
            vectorValue.x *= floatValueA * floatValueB;
            vectorValue.y *= floatValueA * floatValueB;
            vectorValue.z *= floatValueA * floatValueB;

            return vectorValue;

        }

        public static void AddMagnitude(ref Vector3 vector, float magnitude)
        {
            if (vector == Vector3.zero)
                return;

            float vectorMagnitude = Vector3.Magnitude(vector);
            Vector3 vectorDirection = vector / vectorMagnitude;

            vector += vectorDirection * magnitude;
        }

        public static void ChangeMagnitude(ref Vector3 vector, float magnitude)
        {
            if (vector == Vector3.zero)
                return;

            Vector3 vectorDirection = Vector3.Normalize(vector);
            vector = vectorDirection * magnitude;
        }

        public static void ChangeDirection(ref Vector3 vector, Vector3 direction)
        {
            if (vector == Vector3.zero)
                return;

            float vectorMagnitude = Vector3.Magnitude(vector);
            vector = direction * vectorMagnitude;
        }

        public static void ChangeDirectionOntoPlane(ref Vector3 vector, Vector3 planeNormal)
        {
            if (vector == Vector3.zero)
                return;

            Vector3 direction = Vector3.Normalize(Vector3.ProjectOnPlane(vector, planeNormal));
            float vectorMagnitude = Vector3.Magnitude(vector);
            vector = direction * vectorMagnitude;
        }

        public static void GetMagnitudeAndDirection(this Vector3 vector, out Vector3 direction, out float magnitude)
        {
            magnitude = Vector3.Magnitude(vector);
            direction = Vector3.Normalize(vector);
        }

        /// <summary>
        /// Projects an input vector onto the tangent of a given plane (defined by its normal).
        /// </summary>
        public static Vector3 ProjectOnTangent(Vector3 inputVector, Vector3 planeNormal, Vector3 up)
        {
            Vector3 inputVectorDirection = Vector3.Normalize(inputVector);

            if (inputVectorDirection == -up)
            {
                inputVector += planeNormal * 0.01f;
            }
            else if (inputVectorDirection == up)
            {
                return Vector3.zero;
            }

            Vector3 rotationAxis = GetPerpendicularDirection(inputVector, up);
            Vector3 tangent = GetPerpendicularDirection(planeNormal, rotationAxis);

            return Multiply(tangent, Vector3.Magnitude(inputVector));
        }

        /// <summary>
        /// Projects an input vector onto plane A and plane B orthonormal direction.
        /// </summary>
        public static Vector3 DeflectVector(Vector3 inputVector, Vector3 planeA, Vector3 planeB, bool maintainMagnitude = false)
        {
            Vector3 direction = GetPerpendicularDirection(planeA, planeB);

            if (maintainMagnitude)
                return direction * inputVector.magnitude;
            else
                return Vector3.Project(inputVector, direction);
        }

        public static Vector3 GetPerpendicularDirection(Vector3 vectorA, Vector3 vectorB)
        {
            return Vector3.Normalize(Vector3.Cross(vectorA, vectorB));
        }

        public static float GetTriangleValue(float center, float height, float width, float independentVariable, float minIndependentVariableLimit = Mathf.NegativeInfinity, float maxIndependentVariableLimit = Mathf.Infinity)
        {
            float minValue = center - width / 2f;
            float maxValue = center + width / 2f;

            if (independentVariable < minValue || independentVariable > maxValue)
            {
                return 0f;
            }
            else if (independentVariable < center)
            {
                return height * (independentVariable - minValue) / (center - minValue);
            }
            else
            {
                return -height * (independentVariable - center) / (maxValue - center) + height;
            }
        }


        /// <summary>
        /// Makes a value greater than or equal to zero (default value).
        /// </summary>
        public static void SetPositive<T>(ref T value) where T : System.IComparable<T>
        {
            SetMin<T>(ref value, default(T));
        }

        /// <summary>
        /// Makes a value less than or equal to zero (default value).
        /// </summary>
        public static void SetNegative<T>(ref T value) where T : System.IComparable<T>
        {
            SetMax<T>(ref value, default(T));
        }

        /// <summary>
        /// Makes a value greater than or equal to a minimum value.
        /// </summary>
        public static void SetMin<T>(ref T value, T minValue) where T : System.IComparable<T>
        {
            bool isLess = value.CompareTo(minValue) < 0;

            if (isLess)
                value = minValue;
        }

        /// <summary>
        /// Makes a value less than or equal to a maximum value.
        /// </summary>
        public static void SetMax<T>(ref T value, T maxValue) where T : System.IComparable<T>
        {
            bool isGreater = value.CompareTo(maxValue) > 0;

            if (isGreater)
                value = maxValue;
        }

        /// <summary>
        /// Limits a value range from a minimum value to a maximum value (similar to Mathf.Clamp).
        /// </summary>
        public static void SetRange<T>(ref T value, T minValue, T maxValue) where T : System.IComparable<T>
        {
            SetMin<T>(ref value, minValue);
            SetMax<T>(ref value, maxValue);
        }


        /// <summary>
        /// Returns true if the target value is between a and b ( both exclusive ). 
        /// To include the limits values set the "inclusive" parameter to true.
        /// </summary>
        public static bool isBetween(float target, float a, float b, bool inclusive = false)
        {

            if (b > a)
                return (inclusive ? target >= a : target > a) && (inclusive ? target <= b : target < b);
            else
                return (inclusive ? target >= b : target > b) && (inclusive ? target <= a : target < a);
        }

        /// <summary>
        /// Returns true if the target value is between a and b ( both exclusive ). 
        /// To include the limits values set the "inclusive" parameter to true.
        /// </summary>
        public static bool isBetween(int target, int a, int b, bool inclusive = false)
        {

            if (b > a)
                return (inclusive ? target >= a : target > a) && (inclusive ? target <= b : target < b);
            else
                return (inclusive ? target >= b : target > b) && (inclusive ? target <= a : target < a);

        }


        public static bool isCloseTo(Vector3 input, Vector3 target, float tolerance)
        {
            return Vector3.Distance(input, target) <= tolerance;

        }

        public static bool isCloseTo(float input, float target, float tolerance)
        {
            return Mathf.Abs(target - input) <= tolerance;
        }

        public static Vector3 TransformVectorUnscaled(this Transform transform, Vector3 vector)
        {
            return transform.rotation * vector;
        }

        public static Vector3 InverseTransformVectorUnscaled(this Transform transform, Vector3 vector)
        {
            return Quaternion.Inverse(transform.rotation) * vector;
        }

        public static Vector3 RotatePointAround(Vector3 point, Vector3 center, float angle, Vector3 axis)
        {

            Quaternion rotation = Quaternion.AngleAxis(angle, axis);

            Vector3 pointToCenter = center - point;

            Vector3 rotatedPointToCenter = rotation * pointToCenter;

            return center - rotatedPointToCenter;


        }



        public static T GetOrAddComponent<T>(this GameObject targetGameObject, bool includeChildren = false) where T : Component
        {
            T existingComponent = includeChildren ? targetGameObject.GetComponentInChildren<T>() : targetGameObject.GetComponent<T>();
            if (existingComponent != null)
            {
                return existingComponent;
            }

            T component = targetGameObject.AddComponent<T>();

            return component;
        }

        /// <summary>
        /// Gets a "target" component within a particular branch (inside the hierarchy). The branch is defined by the "branch root object", which is also defined by the chosen 
        /// "branch root component". The returned component must come from a child of the "branch root object".
        /// </summary>
        /// <param name="callerComponent"></param>
        /// <param name="includeInactive">Include inactive objects?</param>
        /// <typeparam name="T1">Branch root component type.</typeparam>
        /// <typeparam name="T2">Target component type.</typeparam>
        /// <returns>The target component.</returns>
        public static T2 GetComponentInBranch<T1, T2>(this Component callerComponent, bool includeInactive = true) where T1 : Component where T2 : Component
        {
            T1[] rootComponents = callerComponent.transform.root.GetComponentsInChildren<T1>(includeInactive);

            if (rootComponents.Length == 0)
            {
                Debug.LogWarning($"Root component: No objects found with {typeof(T1).Name} component");
                return null;
            }

            for (int i = 0; i < rootComponents.Length; i++)
            {
                T1 rootComponent = rootComponents[i];

                // Is the caller a child of this root?
                if (!callerComponent.transform.IsChildOf(rootComponent.transform) && !rootComponent.transform.IsChildOf(callerComponent.transform))
                    continue;

                T2 targetComponent = rootComponent.GetComponentInChildren<T2>(includeInactive);

                if (targetComponent == null)
                    continue;

                return targetComponent;

            }

            return null;

        }

        /// <summary>
        /// Gets a "target" component within a particular branch (inside the hierarchy). The branch is defined by the "branch root object", which is also defined by the chosen 
        /// "branch root component". The returned component must come from a child of the "branch root object".
        /// </summary>
        /// <param name="callerComponent"></param>
        /// <param name="includeInactive">Include inactive objects?</param>
        /// <typeparam name="T1">Target component type.</typeparam>	
        /// <returns>The target component.</returns>
        public static T1 GetComponentInBranch<T1>(this Component callerComponent, bool includeInactive = true) where T1 : Component
        {
            return callerComponent.GetComponentInBranch<T1, T1>(includeInactive);
        }



        public static bool IsNullOrEmpty(this string target)
        {
            return target == null || target.Length == 0;
        }

        public static bool IsNullOrWhiteSpace(this string target)
        {
            if (target == null)
                return true;

            for (int i = 0; i < target.Length; i++)
            {
                if (target[i] != ' ')
                    return false;
            }

            return true;
        }



        public static string Between(this string targetString, string firstString, string lastString)
        {
            int start = targetString.IndexOf(firstString) + firstString.Length;
            int end = targetString.IndexOf(lastString);

            if (end - start < 0)
                return "";

            return targetString.Substring(start, end - start);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool BelongsToLayerMask(this int layer, int layerMask) =>
            (layerMask & (1 << layer)) > 0;

        public static T1 GetOrAddComponent<T1>(this GameObject gameObject) where T1 : Component
        {
            if (!gameObject.TryGetComponent(out T1 component))
                component = gameObject.AddComponent<T1>();

            return component;
        }

        public static T1 GetOrAddComponent<T1, T2>(this GameObject gameObject) where T1 : Component where T2 : Component
        {
            if (!gameObject.TryGetComponent(out T1 component))
            {
                if (gameObject.TryGetComponent(out T2 requiredComponent))
                    component = gameObject.AddComponent<T1>();
            }

            return component;
        }

        public static T1 GetOrAddComponent<T1>(this Component baseComponent) where T1 : Component
        {
            if (!baseComponent.TryGetComponent(out T1 component))
                component = baseComponent.gameObject.AddComponent<T1>();

            return component;
        }

        public static T1 GetOrAddComponent<T1, T2>(this Component baseComponent) where T1 : Component where T2 : Component
        {
            if (!baseComponent.TryGetComponent(out T1 component))
            {
                if (baseComponent.TryGetComponent(out T2 requiredComponent))
                    component = baseComponent.gameObject.AddComponent<T1>();
            }

            return component;
        }

        public static T1 GetOrAddComponent<T1, T2>(this Component baseComponent, T2 requiredComponentType) where T1 : Component where T2 : System.Type
        {
            if (!baseComponent.TryGetComponent(out T1 component))
            {
                if (baseComponent.TryGetComponent(out T2 requiredComponent))
                    component = (T1)baseComponent.gameObject.AddComponent(requiredComponentType);
            }

            return component;
        }

        /// <summary>
        /// Gets a particular value from this dictionary. If the value isn't there, it gets added.
        /// </summary>
        /// <typeparam name="T1">Key type.</typeparam>
        /// <typeparam name="T2">Value type.</typeparam>
        /// <param name="dictionary">A dictionary container.</param>
        /// <param name="key">The key parameter</param>
        /// <param name="addIfNull">Indicates if the component should be added if it doesn't exist.</param>
        /// <returns></returns>
        public static T2 GetOrRegisterValue<T1, T2>(this Dictionary<T1, T2> dictionary, T1 key, bool addIfNull = false) where T1 : Component where T2 : Component
        {
            if (key == null)
                return null;

            bool found = dictionary.TryGetValue(key, out T2 value);

            if (!found)
            {
                value = addIfNull ? key.gameObject.GetOrAddComponent<T2>() : key.GetComponent<T2>();

                if (value != null)
                    dictionary.Add(key, value);
            }

            return value;
        }

        /// <summary>
        /// Gets a particular value from this dictionary. If the value isn't there, it gets added.
        /// </summary>
        /// <typeparam name="T1">Key type.</typeparam>
        /// <typeparam name="T2">Value type.</typeparam>
        /// <typeparam name="T3">Required component type.</typeparam>
        /// <param name="dictionary">A dictionary container.</param>
        /// <param name="key">The key parameter</param>
        /// <param name="addIfNull">Indicates if the component should be added if it doesn't exist and the 
        /// required component exist.</param>
        /// <returns></returns>
        public static T2 GetOrRegisterValue<T1, T2, T3>(this Dictionary<T1, T2> dictionary, T1 key, bool addIfNull = false) where T1 : Component where T2 : Component where T3 : Component
        {
            if (key == null)
                return null;

            bool found = dictionary.TryGetValue(key, out T2 value);

            if (!found)
            {
                value = addIfNull ? key.gameObject.GetOrAddComponent<T2, T3>() : key.GetComponent<T2>();

                if (value != null)
                    dictionary.Add(key, value);
            }

            return value;
        }


        public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis)
        {
            float angle = Vector3.Angle(from, to);
            Vector3 cross = Vector3.Cross(from, to);
            cross.Normalize();

            float sign = cross == axis ? 1f : -1f;

            return sign * angle;

        }

        public static void DebugRay(Vector3 point, Vector3 direction = default(Vector3), float duration = 2f, Color color = default(Color))
        {
            Vector3 drawDirection = direction == default(Vector3) ? Vector3.up : direction;
            Color drawColor = color == default(Color) ? Color.blue : color;

            Debug.DrawRay(point, drawDirection, drawColor, duration);
        }

        public static void DrawArrowGizmo(Vector3 start, Vector3 end, Color color, float radius = 0.25f)
        {
            Gizmos.color = color;
            Gizmos.DrawLine(start, end);

            Gizmos.DrawRay(
                end,
                Quaternion.AngleAxis(45, Vector3.forward) * Vector3.Normalize(start - end) * radius
            );

            Gizmos.DrawRay(
                end,
                Quaternion.AngleAxis(-45, Vector3.forward) * Vector3.Normalize(start - end) * radius
            );
        }

        public static void DrawGizmoCross(Vector3 point, float radius, Color color)
        {
            Gizmos.color = color;

            Gizmos.DrawRay(
                point + Vector3.up * 0.5f * radius,
                Vector3.down * radius
            );

            Gizmos.DrawRay(
                point + Vector3.right * 0.5f * radius,
                Vector3.left * radius
            );
        }

        public static void DrawDebugCross(Vector3 point, float radius, Color color, float angleOffset = 0f)
        {

            Debug.DrawRay(
                point + Quaternion.Euler(0, 0, angleOffset) * Vector3.up * 0.5f * radius,
                Quaternion.Euler(0, 0, angleOffset) * Vector3.down * radius,
                color
            );

            Debug.DrawRay(
                point + Quaternion.Euler(0, 0, angleOffset) * Vector3.right * 0.5f * radius,
                Quaternion.Euler(0, 0, angleOffset) * Vector3.left * radius,
                color
            );
        }



        #region Animator

        /// <summary>
        /// Gets the current clip effective length, that is, the original length divided by the playback speed. The length value is always positive, regardless of the speed sign. 
        /// It returns false if the clip is not valid.
        /// </summary>
        public static bool GetCurrentClipLength(this Animator animator, ref float length)
        {
            if (animator.runtimeAnimatorController == null)
                return false;

            AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(0);

            if (clipInfo.Length == 0)
                return false;


            float clipLength = clipInfo[0].clip.length;
            float speed = animator.GetCurrentAnimatorStateInfo(0).speed;


            length = Mathf.Abs(clipLength / speed);

            return true;
        }

        public static bool MatchTarget(this Animator animator, Vector3 targetPosition, Quaternion targetRotation, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime)
        {
            if (animator.runtimeAnimatorController == null)
                return false;

            if (animator.isMatchingTarget)
                return false;

            if (animator.IsInTransition(0))
                return false;

            MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 1f);

            animator.MatchTarget(
                targetPosition,
                targetRotation,
                avatarTarget,
                weightMask,
                startNormalizedTime,
                targetNormalizedTime
            );


            return true;
        }

        public static bool MatchTarget(this Animator animator, Vector3 targetPosition, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime)
        {
            if (animator.runtimeAnimatorController == null)
                return false;

            if (animator.isMatchingTarget)
                return false;

            if (animator.IsInTransition(0))
                return false;

            MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 0f);

            animator.MatchTarget(
                targetPosition,
                Quaternion.identity,
                avatarTarget,
                weightMask,
                startNormalizedTime,
                targetNormalizedTime
            );

            return true;
        }

        public static bool MatchTarget(this Animator animator, Transform target, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime)
        {
            if (animator.runtimeAnimatorController == null)
                return false;

            if (animator.isMatchingTarget)
                return false;

            if (animator.IsInTransition(0))
                return false;

            MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 1f);

            animator.MatchTarget(
                target.position,
                target.rotation,
                avatarTarget,
                weightMask,
                startNormalizedTime,
                targetNormalizedTime
            );


            return true;
        }

        public static bool MatchTarget(this Animator animator, Transform target, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime, MatchTargetWeightMask weightMask)
        {
            if (animator.runtimeAnimatorController == null)
                return false;

            if (animator.isMatchingTarget)
                return false;

            if (animator.IsInTransition(0))
                return false;

            animator.MatchTarget(
                target.position,
                target.rotation,
                AvatarTarget.Root,
                weightMask,
                startNormalizedTime,
                targetNormalizedTime
            );


            return true;
        }


        #endregion

        // ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        // ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        // ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

#if UNITY_EDITOR

        public static List<T> FindInterfaces<T>()
        {
            List<T> interfaces = new List<T>();

            GameObject[] rootGameObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();

            foreach (var rootGameObject in rootGameObjects)
            {
                T[] childrenInterfaces = rootGameObject.GetComponentsInChildren<T>();
                foreach (T childInterface in childrenInterfaces)
                {
                    interfaces.Add(childInterface);
                }
            }

            return interfaces;
        }


        public static MethodInfo[] GetMethods(this MonoBehaviour monoBehaviour)
        {
            MonoScript monoScript = MonoScript.FromMonoBehaviour(monoBehaviour);
            MethodInfo[] methods = monoScript.GetClass().GetMethods();

            return methods;
        }

        public static MethodInfo[] GetMethods(this ScriptableObject scriptableObject)
        {
            MonoScript monoScript = MonoScript.FromScriptableObject(scriptableObject);
            MethodInfo[] methods = monoScript.GetClass().GetMethods();

            return methods;
        }

        public static System.Type[] GetAllDerivedObjects<T>(bool allowAbstract = true) where T : Component
        {
            List<System.Type> result = new List<System.Type>();

            var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetTypes();
                foreach (var type in types)
                {
                    if (type.IsSubclassOf(typeof(T)))
                    {
                        if (type.IsAbstract)
                        {
                            if (allowAbstract)
                                result.Add(type);
                        }
                        else
                        {
                            result.Add(type);
                        }
                    }
                }
            }

            return result.ToArray();
        }

        public static T GetInterface<T>(this MonoBehaviour monoBehaviour)
        {
            T[] walkInterfaces = monoBehaviour.GetComponents<T>();
            for (int i = 0; i < walkInterfaces.Length; i++)
            {
                T @interface = walkInterfaces[i];
                Object interfaceObject = @interface as object as Object;
                if (interfaceObject == monoBehaviour)
                    return @interface;
            }

            return default(T);
        }

        public static System.Type[] GetAllDerivedComponents(System.Type componentType, bool allowAbstract = true)
        {
            if (!componentType.IsSubclassOf(typeof(Component)))
                return null;

            List<System.Type> result = new List<System.Type>();

            var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetTypes();
                foreach (var type in types)
                {
                    if (type.IsSubclassOf(componentType))
                    {
                        if (type.IsAbstract)
                        {
                            if (allowAbstract)
                                result.Add(type);
                        }
                        else
                        {
                            result.Add(type);
                        }
                    }
                }
            }

            return result.ToArray();
        }

        public static System.Type[] GetAllDerivedObjectsClass<T>(bool allowAbstract = true) where T : class
        {
            List<System.Type> result = new List<System.Type>();

            var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetTypes();
                foreach (var type in types)
                {
                    if (type.IsSubclassOf(typeof(T)))
                    {
                        if (type.IsAbstract)
                        {
                            if (allowAbstract)
                                result.Add(type);
                        }
                        else
                        {
                            result.Add(type);
                        }
                    }
                }
            }

            return result.ToArray();
        }

        public static void DrawMonoBehaviourField<T>(T target) where T : MonoBehaviour
        {
            GUI.enabled = false;
            EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(target), typeof(T), false);
            GUI.enabled = true;
        }

        public static void DrawScriptableObjectField<T>(T target) where T : ScriptableObject
        {
            GUI.enabled = false;
            EditorGUILayout.ObjectField("Script:", MonoScript.FromScriptableObject(target), typeof(T), false);
            GUI.enabled = true;
        }

        public static void DrawEditorLayoutHorizontalLine(Color color, int thickness = 1, int padding = 10)
        {
            Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(padding + thickness));

            rect.height = thickness;
            rect.y += padding / 2;
            //rect.x -= 2;
            //rect.width +=6;

            EditorGUI.DrawRect(rect, color);
        }

        public static void DrawEditorHorizontalLine(ref Rect rect, Color color, int thickness = 1, int padding = 10)
        {
            rect.height = thickness;
            rect.y += padding / 2;
            //rect.x -= 2;
            //rect.width +=6;

            EditorGUI.DrawRect(rect, color);

            rect.y += padding;
            rect.height = EditorGUIUtility.singleLineHeight;
        }

        public static void DrawWireCapsule(Vector3 position, Quaternion rotation, float radius, float height, Color color = default(Color))
        {
            if (color != default(Color))
                Handles.color = color;

            Matrix4x4 angleMatrix = Matrix4x4.TRS(position, rotation, Handles.matrix.lossyScale);

            using (new Handles.DrawingScope(angleMatrix))
            {
                var pointOffset = (height - (radius * 2)) / 2;

                //draw sideways
                Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.left, Vector3.back, -180, radius);
                Handles.DrawLine(new Vector3(0, pointOffset, -radius), new Vector3(0, -pointOffset, -radius));
                Handles.DrawLine(new Vector3(0, pointOffset, radius), new Vector3(0, -pointOffset, radius));
                Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.left, Vector3.back, 180, radius);

                //draw frontways
                Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.back, Vector3.left, 180, radius);
                Handles.DrawLine(new Vector3(-radius, pointOffset, 0), new Vector3(-radius, -pointOffset, 0));
                Handles.DrawLine(new Vector3(radius, pointOffset, 0), new Vector3(radius, -pointOffset, 0));
                Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.back, Vector3.left, -180, radius);

                //draw center
                Handles.DrawWireDisc(Vector3.up * pointOffset, Vector3.up, radius);
                Handles.DrawWireDisc(Vector3.down * pointOffset, Vector3.up, radius);

            }
        }




        public static void DrawArray(SerializedProperty rootProperty, string namePropertyString = null, bool showAddElement = true, bool removeIconToTheRight = true)
        {
            if (!rootProperty.isArray)
                return;


            for (int i = 0; i < rootProperty.arraySize; i++)
            {
                SerializedProperty item = rootProperty.GetArrayElementAtIndex(i);
                item.isExpanded = true;

                GUILayout.BeginVertical(EditorStyles.helpBox);

                // if( removeIconToTheRight )
                // 	GUILayout.BeginHorizontal();

                CustomUtilities.DrawArrayElement(item, namePropertyString);

                GUILayout.Space(20);


                if (GUILayout.Button("Remove element", EditorStyles.miniButton))
                {
                    rootProperty.DeleteArrayElementAtIndex(i);
                    break;
                }

                // if( removeIconToTheRight )
                // 	GUILayout.EndHorizontal();

                GUILayout.EndVertical();

                GUILayout.Space(20);

            }


            if (showAddElement)
                if (GUILayout.Button("Add element"))
                    rootProperty.arraySize++;


        }

        public static void DrawArrayElement(SerializedProperty property, string namePropertyString = null, bool skipFirstChild = false)
        {
            // if( property.isArray )
            //     return;

            EditorGUI.indentLevel++;

            SerializedProperty nameProperty = null;

            if (namePropertyString != null)
            {
                nameProperty = property.FindPropertyRelative(namePropertyString);
                if (nameProperty != null)
                {
                    string name = nameProperty.stringValue;

                    EditorGUILayout.LabelField(name, EditorStyles.boldLabel);
                }

                EditorGUI.indentLevel++;
            }




            SerializedProperty itr = property.Copy();

            bool enterChildren = true;

            while (itr.Next(enterChildren))
            {

                if (SerializedProperty.EqualContents(itr, property.GetEndProperty()))
                    break;

                if (enterChildren && skipFirstChild)
                {
                    enterChildren = false;
                    continue;
                }

                EditorGUILayout.PropertyField(itr, enterChildren);

                enterChildren = false;

            }

            EditorGUI.indentLevel = nameProperty != null ? EditorGUI.indentLevel - 2 : EditorGUI.indentLevel - 1;


        }

        public static void DrawArrayElement(Rect rect, SerializedProperty property, string namePropertyString = null, bool skipFirstChild = false)
        {
            if (property.isArray)
                return;

            EditorGUI.indentLevel++;

            SerializedProperty nameProperty = null;

            if (namePropertyString != null)
            {
                nameProperty = property.FindPropertyRelative(namePropertyString);
                if (nameProperty != null)
                {
                    string name = nameProperty.stringValue;


                    EditorGUI.LabelField(rect, name, EditorStyles.boldLabel);
                    rect.y += rect.height;


                }

                EditorGUI.indentLevel++;
            }




            SerializedProperty itr = property.Copy();

            bool enterChildren = true;



            while (itr.Next(enterChildren))
            {

                if (SerializedProperty.EqualContents(itr, property.GetEndProperty()))
                    break;

                if (enterChildren && skipFirstChild)
                {
                    enterChildren = false;
                    continue;
                }

                EditorGUI.PropertyField(rect, itr, enterChildren);
                rect.y += rect.height;

                enterChildren = false;

            }

            EditorGUI.indentLevel = nameProperty != null ? EditorGUI.indentLevel - 2 : EditorGUI.indentLevel - 1;


        }

#endif

    }





}
